package com.qingzhuge.resource.autoconfigure;

import com.qingzhuge.autoconfigure.QingTingProperties;
import com.qingzhuge.common.enums.EAuthorityType;
import com.qingzhuge.common.enums.EResourceType;
import com.qingzhuge.context.SpringContext;
import com.qingzhuge.core.annotation.AResource;
import com.qingzhuge.resource.entity.SysResource;
import com.qingzhuge.resource.service.ISysResourceService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.SpelNode;
import org.springframework.expression.spel.ast.MethodReference;
import org.springframework.expression.spel.ast.StringLiteral;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.*;

/**
 * @author : zero.xiao
 * @description :
 * @date :2018/8/20 0020 下午 17:45
 * @modified :
 * @since : 1.0.0
 */
@Slf4j
@Order
@Component
public class SysResourceApplicationRunner implements ApplicationRunner {

    private static final String AUTHORIZE_ROLE = "Role";
    private static final String AUTHORIZE_AUTHORITY = "Authority";
    private static final String AUTHORIZE_PERMISSION = "Permission";

    private ISysResourceService resourceService;

    private QingTingProperties properties;

    private Map<String, String> preAuthorizeMap = new HashMap<>();

    @Autowired
    public void setProperties(QingTingProperties properties) {
        this.properties = properties;
    }

    @Resource
    public void setResourceService(ISysResourceService resourceService) {
        this.resourceService = resourceService;
    }

    @Override
    public void run(ApplicationArguments args) {
        if (properties.isInitData() || resourceService.count() < 1) {
            preAuthorizeMap.put("hasAnyRole", AUTHORIZE_ROLE);
            preAuthorizeMap.put("hasRole", AUTHORIZE_ROLE);
            preAuthorizeMap.put("hasAnyAuthority", AUTHORIZE_AUTHORITY);
            preAuthorizeMap.put("hasAuthority", AUTHORIZE_AUTHORITY);
            preAuthorizeMap.put("hasPermission", AUTHORIZE_PERMISSION);
            log.debug("初始化菜单信息...");
            String[] beanNames = SpringContext.getBeanNamesForAnnotation(Controller.class);
            for (String beanName : beanNames) {
                AResource rootAnnotation = SpringContext.findAnnotationOnBean(beanName, AResource.class);
                if (rootAnnotation == null) {
                    continue;
                }
                RequestMapping mapping = SpringContext.findAnnotationOnBean(beanName, RequestMapping.class);
                StringBuilder path = new StringBuilder();
                if (mapping == null) {
                    setPath(path, new String[]{rootAnnotation.path()});
                } else {
                    setPath(path, mapping.path());
                }
                //创建菜单
                SysResource pResource = createResource(RequestMethod.GET.name(), rootAnnotation, path.toString(), null, null, null, new SysResource(rootAnnotation.name()));

                Class<?> handlerType = SpringContext.getType(beanName);
                //==========================Mapping===================================
                ReflectionUtils.doWithMethods(handlerType, method -> {
                    //获取方法上的菜单注解
                    AResource annotation = AnnotationUtils.findAnnotation(method, AResource.class);
                    if (annotation != null) {
                        StringBuilder conpath = new StringBuilder(path);
                        if (pResource.isNew()) {
                            String parentName = annotation.catalog();
                            if (StringUtils.isEmpty(parentName)) {
                                parentName = rootAnnotation.name();
                            }
                            pResource.setName(parentName);
                        }
                        GetMapping getMapping = AnnotationUtils.findAnnotation(method, GetMapping.class);
                        PostMapping postMapping = AnnotationUtils.findAnnotation(method, PostMapping.class);
                        DeleteMapping deleteMapping = AnnotationUtils.findAnnotation(method, DeleteMapping.class);
                        PutMapping putMapping = AnnotationUtils.findAnnotation(method, PutMapping.class);
                        //角色
                        List<String> roleList = new ArrayList<>();
                        //权限
                        List<String> authorityList = new ArrayList<>();
                        //
                        List<String> permissionList = new ArrayList<>();
                        PreAuthorize preAuthorize = AnnotationUtils.findAnnotation(method, PreAuthorize.class);
                        if (null != preAuthorize) {
                            String authorizeValue = preAuthorize.value();
                            ExpressionParser expressionParser = new SpelExpressionParser();
                            Expression expression = expressionParser.parseExpression(authorizeValue);
                            if (expression instanceof SpelExpression) {
                                SpelExpression spelExpression = ((SpelExpression) expression);
                                SpelNode ast = spelExpression.getAST();
                                parseNode(roleList, authorityList, permissionList, ast);
                            }
                        }
                        if (getMapping != null) {
                            String[] mappedPattern = getMapping.value();
                            setPath(conpath, mappedPattern);
                            createResource(RequestMethod.GET.name(), annotation, conpath.toString(), roleList, authorityList, permissionList, pResource);
                        } else if (postMapping != null) {
                            String[] mappedPattern = postMapping.value();
                            setPath(conpath, mappedPattern);
                            createResource(RequestMethod.POST.name(), annotation, conpath.toString(), roleList, authorityList, permissionList, pResource);
                        } else if (deleteMapping != null) {
                            String[] mappedPattern = deleteMapping.value();
                            setPath(conpath, mappedPattern);
                            createResource(RequestMethod.DELETE.name(), annotation, conpath.toString(), roleList, authorityList, permissionList, pResource);
                        } else if (putMapping != null) {
                            String[] mappedPattern = putMapping.value();
                            setPath(conpath, mappedPattern);
                            createResource(RequestMethod.PUT.name(), annotation, conpath.toString(), roleList, authorityList, permissionList, pResource);
                        }
                    }
                }, ReflectionUtils.USER_DECLARED_METHODS);
            }
        }
    }

    private void parseNode(List<String> roleList, List<String> authorityList, List<String> permissionList, SpelNode ast) {
        if (ast instanceof MethodReference) {
            parseMethodReference(roleList, authorityList, permissionList, ast);
        } else {
            for (int i = 0; i < ast.getChildCount(); i++) {
                SpelNode spelNode = ast.getChild(i);
                parseNode(roleList, authorityList, permissionList, spelNode);
            }
        }
    }

    private void parseMethodReference(List<String> authRoleList, List<String> authAuthorityList, List<String> authPermissionList, SpelNode ast) {
        String authorize = "";
        if (ast instanceof MethodReference) {
            MethodReference reference = (MethodReference) ast;
            authorize = preAuthorizeMap.get(reference.getName());
        }
        for (int i = 0; i < ast.getChildCount(); i++) {
            SpelNode spelNode = ast.getChild(i);
            String value = ((StringLiteral) spelNode).getOriginalValue();
            if (StringUtils.isNotBlank(value)) {
                value = value.replace("'", "");
                if (AUTHORIZE_ROLE.equals(authorize)) {
                    authRoleList.add(value);
                } else if (AUTHORIZE_AUTHORITY.equals(authorize)) {
                    authAuthorityList.add(value);
                } else if (AUTHORIZE_PERMISSION.equals(authorize)) {
                    authPermissionList.add(value);
                } else {
                    authRoleList.add(value);
                }
            }
        }
    }

    private void setPath(StringBuilder conpath, String[] mappedPattern) {
        String pathRoot = "/";
        if (null != mappedPattern && mappedPattern.length > 0) {
            String mappPath = mappedPattern[0];
            String defUrl = "#";
            if (!Objects.equals(defUrl, mappPath) && !mappPath.startsWith(pathRoot)) {
                if (Objects.equals(defUrl, conpath.toString())) {
                    conpath.delete(0, 1);
                }
                conpath.append(pathRoot);
                conpath.append(mappPath);
            } else {
                conpath.append(mappPath);
            }
        } else {
            conpath.append(pathRoot);
        }
    }

    private SysResource createResource(String method, AResource aResource, String path, List<String> roleList, List<String> authorityList, List<String> permissionList, SysResource parentMenu) {
        SysResource menu = resourceService.findByUrlAndRequestMethod(path, method);
        if (menu == null) {
            menu = new SysResource();
            EAuthorityType authorityType = aResource.authority();
            if (StringUtils.isNotBlank(aResource.name())) {
                menu.setName(aResource.name());
            } else if (aResource.type().equals(EResourceType.button)) {
                if (authorityType != EAuthorityType.DEFAULT) {
                    menu.setName(authorityType.value());
                }
            }
            menu.setUrl(path);
            menu.setRequestMethod(method);
            menu.setType(aResource.type());

            if (null != permissionList && permissionList.size() > 0) {
                String permissions = String.join(",",permissionList);
                menu.setPermissions(permissions);
            }
            if (null != roleList && roleList.size() > 0) {

                String roles = String.join(",", roleList);
                menu.setRole(roles);
            }
            if (null != authorityList && authorityList.size() > 0) {
                String authority = String.join(",",authorityList);
                menu.setAuthority(authority);
            }
            if (parentMenu.isNew()) {
                String parentName = aResource.catalog();
                parentMenu = resourceService.findByNameAndType(parentName, EResourceType.catalog);
                if (parentMenu == null) {
                    parentMenu = new SysResource();
                    parentMenu.setName(parentName);
                    parentMenu.setType(EResourceType.catalog);
                    parentMenu.setUrl("#");
                    parentMenu.setDisplay(true);
                    parentMenu = resourceService.save(parentMenu);
                }
            }
            menu.setParent(parentMenu);
            menu.setDisplay(true);
            return resourceService.save(menu);
        }
        return menu;
    }
}